fix(lambda): use scope graph env for edit capture checks#420
Conversation
|
Warning Review limit reached
More reviews will be available in 46 minutes and 38 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
lambda-editor | ad9a487 | Commit Preview URL Branch Preview URL |
May 31 2026, 10:18 AM |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2061f945f6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ) -> Bool { | ||
| let env = @scope.enclosing_env(g, node_id) | ||
| let mut captured = false | ||
| free_names.each(fn(v) { if env.contains(v) { captured = true } }) |
There was a problem hiding this comment.
Avoid treating same module binding as capture
When the initializer already refers to an earlier module binding, e.g. let y = 1\nlet x = y\nx, free_vars(def.1.kind, empty) contains y and enclosing_env at the body also contains that same y, so this name-only membership check reports capture and blocks both single/all inlining. That replacement is not a capture—the inserted y resolves to the same declaration as the initializer did—so the guard needs to compare resolved declarations or exclude names already bound in the initializer’s own environment instead of rejecting any matching enclosing module name.
Useful? React with 👍 / 👎.
Benchmark Comparison ReportComparing PR branch against Main Module BenchmarksBase branch: PR branch: event-graph-walker BenchmarksBase branch: PR branch: Benchmarks run with |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 35f58c88e2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let target_decl = declaration_id_for_name_at_node( | ||
| g, flat_proj, source_map, target_node_id, v, | ||
| ) |
There was a problem hiding this comment.
Ignore the binding being removed during inline checks
When the initializer has a free occurrence with the same name as the binding being inlined, e.g. let x = x\nx, this lookup resolves target_decl to the current module declaration because the graph is built before the inline deletes that declaration. The source occurrence is correctly unresolved in its own initializer, so the comparison below reports a rebind and rejects the inline, even though after deleting the binding the inserted x would also be unresolved and semantics are preserved. Treat the module decl being inlined as absent when computing the target declaration.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ad9a487acb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| collect_var_usages(source_node, name, var_ids) | ||
| var_ids.any(fn(vid) { | ||
| @scope.declaration(g, vid).map(fn(d) { d.id }) != target_decl |
There was a problem hiding this comment.
Ignore locally bound occurrences during rebind checks
When the initializer has both a free occurrence and a nested binder with the same name, this collects every Var(name) under source_node, including occurrences that free_vars excluded as locally bound. For example, in let y = 1 let x = y + (λy. y) x, inlining x should be safe because the free y still resolves to the module binding and the inner λy. y remains self-bound, but the inner y resolves to a different declaration than the target and makes this guard reject the edit. Filter the collected ids to only the free occurrences whose original declaration is the one being compared.
Useful? React with 👍 / 👎.
Summary
collect_lam_envhelper to canonical@scope.enclosing_env.collect_lam_envedits API and updatesenclosing_envdocs now that it has edit consumers.Reuse check
Existing APIs considered:
@scope.enclosing_envlang/lambda/scope/query.mbt@scope.declarationlang/lambda/scope/query.mbtfree_varslang/lambda/edits/free_vars.mbtcollect_var_usageslang/lambda/edits/scope.mbtNew helpers added:
name_captured_at_node: private edits-package predicate for one name against@scope.enclosing_envat a node.free_names_captured_at_use_sites: private edits-package predicate for bare-expression extraction, where composite nodes need per-use-site environments.def_cutoff_at_node,scope_id_for_node,root_scope_id,declaration_id_for_name_from_scope,declaration_id_for_name_at_node,declaration_id_for_name_at_module_end: private edits-package helpers for resolving a name at concrete or hypothetical edit sites using the same module cutoff rule.free_names_would_rebind_at_node,free_names_would_rebind_at_module_end,free_name_would_rebind_to: private edits-package predicates that reject only when a free occurrence would resolve to a different declaration after the edit.Test plan
NEW_MOON_MOD=0 moon checkpassesNEW_MOON_MOD=0 moon testpassesgit diff *.mbtireviewed for unintended API surface changesValidation
Results:
lang/lambda/edits: 135/135 passed.mbti: only intended removal is publiccollect_lam_env